/*******************************************************************************
 * @file Table.cpp
 * Implementation of methods for class CTable
 * 
 * @package Dining.
 * 
 * @author mk
 * @date 03/01/2008 13:50:21
 * @version 0.1
 ******************************************************************************/

#include "Table.h"
#include "Philosoph.h"
#ifdef __MSVC_COMPILER_RECOGNIZES_CORRECT_FORWARD_DECLARATION__
#include "Spoon.h"
#endif
#include "Assert.h"
#include "Logging.h"
#include "Globals.h"

#include <boost/thread/thread.hpp>

using namespace Dining;
using namespace std;

CTable::CTable(const unsigned int aCount,
			   unsigned int aPortions) :
			   m_Count(aCount),
			   m_Food(aPortions)
{
	TRACE_FUNC("CTable::CTable");
	
	LOG_INFO("Table created with " <<
		m_Count <<
		" places and " <<
		m_Food.m_Portions <<
		" portions of food.");

	LOG_INFO("Number of cores " << boost::thread::hardware_concurrency());

	/** Create <m_Count> spoons on the table. */
	for (unsigned int i = 0; i < m_Count; ++i)
	{
		CSpoon* s = new CSpoon(i);
		LOG_INFO("Spoon " <<
			s->GetId() <<
			" was put on the table.");

		m_Spoons.push_back(s);
	}
	
	/** Create and place <m_Count> philosophers between 2 spoons. */
	for (unsigned int i = 0; i < m_Count; ++i)
	{
		const unsigned int left = i;
		unsigned int right = i + 1;
		
		/** The last philosopher has access to the first spoon. */
		if (i == (m_Count - 1)) right = 0;
		 
		/** Get pointers to 2 spoons. */
		CSpoon* const leftSpoon = m_Spoons[left];
		CSpoon* const rightSpoon = m_Spoons[right];
		__ASSERT_POINTER(leftSpoon);		
		__ASSERT_POINTER(rightSpoon);
		
		/** Create a philosopher and place him between the 2 spoons and give him access to the food. */
		CPhilosoph* p = new CPhilosoph(i, *leftSpoon, *rightSpoon, this->m_Food);

		LOG_INFO("Philosoph " <<
			p->GetId() <<
			" was put between spoon " <<
			leftSpoon->GetId() <<
			" and spoon " <<
			rightSpoon->GetId() <<
			".");

		m_Philosophs.push_back(p);
	}
}

CTable::~CTable()
{
	TRACE_FUNC("CTable::~CTable");
	
	/** Remove all the philosophers from the table. */
	Philosophs::iterator iter_p;
	for (iter_p = m_Philosophs.begin();
		 iter_p != m_Philosophs.end();
		 iter_p++)
	{
	 	CPhilosoph* p = *iter_p;
	 	delete p;
	}
	
	/** Clean up all the spoons from the table */
	Spoons::iterator iter_s;
	for (iter_s = m_Spoons.begin();
		 iter_s != m_Spoons.end();
		 iter_s++)
	{
	 	CSpoon* s = *iter_s;
	 	delete s;
	}
}

void CTable::Start()
{
	TRACE_FUNC("CTable::Start");
	
	/** Create a thread for every philosoph. */
	boost::thread_group threads;
	
	Philosophs::iterator iter_p;
	for (iter_p = m_Philosophs.begin();
		 iter_p != m_Philosophs.end();
		 iter_p++)
	{
	 	CPhilosoph* p = *iter_p;
		__ASSERT_POINTER(p);

		threads.create_thread(boost::ref(*p));
	}
	
	/** Wait for all the threads to be finished. */
	threads.join_all();

	unsigned int sum = 0;

	/**
	 * Loop through all the philosophs.
	 * Show how much everyone has consumed.
	 */
	for (iter_p = m_Philosophs.begin();
		 iter_p != m_Philosophs.end();
		 iter_p++)
	{
	 	CPhilosoph* p = *iter_p;
		__ASSERT_POINTER(p);

	 	LOG_INFO("Philosoph " <<
			p->GetId() <<
			" has consumed " <<
			p->GetConsumed() <<
			" portions.");

		sum += p->GetConsumed();
	}

	LOG_INFO("Sum of portions " << sum);

	__ASSERT_ALWAYS(sum != m_Food.m_Portions, throw std::runtime_error("Sum of portions does not match!"));
}
